{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import time\n",
    "\n",
    "from typing import Optional, Callable, Dict, Sequence\n",
    "from dataclasses import dataclass, field\n",
    "from tqdm import trange\n",
    "\n",
    "import torch\n",
    "import torch.nn.functional as F\n",
    "import torch.backends.xnnpack\n",
    "\n",
    "from torch import nn\n",
    "from torch.amp import autocast\n",
    "from torch.profiler import profile, schedule, ProfilerActivity"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# KV cache\n",
    "\n",
    "The KV-cache method in language models, specifically in the context of attention mechanisms like those used in Transformer models, refers to a technique where Key (K) and Value (V) vectors from previous attention operations are cached (stored) for reuse in subsequent operations. This method is primarily used to enhance efficiency and speed in autoregressive models, where the prediction of the next token in a sequence depends on the previously generated tokens.\n",
    "\n",
    "By caching the Key and Value pairs from earlier tokens, the model avoids redundant computations for these tokens when processing new tokens in the sequence. This is particularly beneficial in tasks like text generation, where each new token prediction requires considering the entire preceding context. The KV-cache method thus enables faster and more computationally efficient inference, making real-time applications and longer sequence generation more feasible.\n",
    "\n",
    "For more details follow the link https://mett29.github.io/posts/kv-cache/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img width=\"716\" alt=\"image\" src=\"https://github.com/markovka17/dla/assets/20357655/f51e8cc7-ff13-4df1-88ae-23414e09412f\">"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "\n",
    "class SelfAttention(nn.Module):\n",
    "    def __init__(self, embed_size, heads):\n",
    "        super().__init__()\n",
    "        max_batch_size = 32\n",
    "        max_seq_len = 512\n",
    "\n",
    "        # Indicates the number of heads for the Keys and Values\n",
    "        self.n_kv_heads = heads\n",
    "        # Indicates the number of heads for the Queries\n",
    "        self.n_heads_q = heads\n",
    "        # Indicates how many times the Keys and Values should be repeated\n",
    "        self.n_rep = self.n_heads_q // self.n_kv_heads\n",
    "        # Indicates the dimension of each head, that is, the part of the embedding that each head will be responsible for\n",
    "        self.head_dim = embed_size // heads\n",
    "\n",
    "        self.wq = nn.Linear(embed_size, self.n_heads_q * self.head_dim, bias=False)\n",
    "        self.wk = nn.Linear(embed_size, self.n_kv_heads * self.head_dim, bias=False)\n",
    "        self.wv = nn.Linear(embed_size, self.n_kv_heads * self.head_dim, bias=False)\n",
    "        self.wo = nn.Linear(heads * self.head_dim, embed_size, bias=False)\n",
    "\n",
    "        cache_k = torch.zeros((max_batch_size, max_seq_len, self.n_kv_heads, self.head_dim))\n",
    "        cache_v = torch.zeros((max_batch_size, max_seq_len, self.n_kv_heads, self.head_dim))\n",
    "        self.register_buffer(\"cache_k\", cache_k)\n",
    "        self.register_buffer(\"cache_v\", cache_v)\n",
    "\n",
    "    def forward(self, x: torch.Tensor, start_pos: int = None, use_cache: bool = False):\n",
    "        batch_size, seq_len, _ = x.shape  # (B, 1, Dim) \n",
    "        # seq_len equals 1 during the decoding phase and equals the length of the prompt during prefilling\n",
    "        xq = self.wq(x)  # (B, 1, Dim) -> (B, 1, H_Q * Head_Dim)\n",
    "        xk = self.wk(x)  # (B, 1, Dim) -> (B, 1, H_KV * Head_Dim)\n",
    "        xv = self.wv(x)  # (B, 1, Dim) -> (B, 1, H_KV * Head_Dim)\n",
    "\n",
    "        # (B, 1, H_Q * Head_Dim) -> (B, 1, H_Q, Head_Dim)\n",
    "        xq = xq.view(batch_size, seq_len, self.n_heads_q, self.head_dim)\n",
    "        xk = xk.view(batch_size, seq_len, self.n_kv_heads, self.head_dim)\n",
    "        xv = xv.view(batch_size, seq_len, self.n_kv_heads, self.head_dim)\n",
    "\n",
    "        # (B, Seq_Len_KV, H_KV, Head_Dim)\n",
    "        if use_cache:\n",
    "            # Replace the entry in the cache\n",
    "            self.cache_k[:batch_size, start_pos : start_pos + seq_len] = xk\n",
    "            self.cache_v[:batch_size, start_pos : start_pos + seq_len] = xv\n",
    "\n",
    "            # All context\n",
    "            keys = self.cache_k[:batch_size, : start_pos + seq_len]\n",
    "            values = self.cache_v[:batch_size, : start_pos + seq_len]\n",
    "        else:\n",
    "            keys = xk\n",
    "            values = xv\n",
    "\n",
    "        # (B, 1, H_Q, Head_Dim) -> (B, H_Q, 1, Head_Dim)\n",
    "        xq = xq.transpose(1, 2)\n",
    "        keys = keys.transpose(1, 2)\n",
    "        values = values.transpose(1, 2)\n",
    "\n",
    "        scores = torch.matmul(xq, keys.transpose(2, 3)) / math.sqrt(self.head_dim)\n",
    "        scores = F.softmax(scores.float(), dim=-1).type_as(xq)\n",
    "        output = torch.matmul(scores, values)\n",
    "        output = (output.transpose(1, 2).contiguous().view(batch_size, seq_len, -1))\n",
    "        return self.wo(output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 512/512 [00:00<00:00, 3057.72it/s]\n",
      "100%|██████████| 512/512 [00:00<00:00, 3356.51it/s]\n",
      "100%|██████████| 512/512 [00:00<00:00, 1470.68it/s]\n",
      "100%|██████████| 512/512 [00:00<00:00, 1453.83it/s]\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlEAAAGwCAYAAACJjDBkAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA5lBJREFUeJzsnXl4E1Xbxu8s3aEtiLSAKAgoIJuAIIgWtVoEFVwQERUQ4dX3rYqoKIoguKAoCAjKh4qIgCguqIhVZBOkgqyy71AE2lJK9yXr98dkknNOZiZLkyYtz++6eiWZOTNzkk5m7tzPc56js9vtdhAEQRAEQRA+oQ91BwiCIAiCIGoiJKIIgiAIgiD8gEQUQRAEQRCEH5CIIgiCIAiC8AMSUQRBEARBEH5AIoogCIIgCMIPSEQRBEEQBEH4gTHUHajN2Gw2nDlzBnXr1oVOpwt1dwiCIAiC8AK73Y7i4mI0btwYer2630QiKoicOXMGTZs2DXU3CIIgCILwg1OnTuGyyy5TXU8iKojUrVsXgPRPiI+PD3FvCIIgCILwhqKiIjRt2tR5H1eDRFQQkUN48fHxJKIIgiAIoobhKRWHEssJgiAIgiD8gEQUQRAEQRCEH5CIIgiCIAiC8APKiQoxNpsNJpMp1N0giFpNREQEDAZDqLtBEEQtg0RUCDGZTDh+/DhsNluou0IQtZ7ExEQkJydTzTaCIAIGiagQYbfbcfbsWRgMBjRt2lSzmBdBEP5jt9tRVlaG3NxcAECjRo1C3COCIGoLJKJChMViQVlZGRo3bozY2NhQd4cgajUxMTEAgNzcXDRs2JBCewRBBASyP0KE1WoFAERGRoa4JwRxcSD/WDGbzSHuCUEQtQUSUSGG8jMIonqg7xpBEIGGRBRBEARBEIQfkIgiCIIgCILwAxJRRNBYsGABEhMTPbbT6XRYvny5T/tu1qwZZsyY4Ve/QsGJEyeg0+mwc+fOgO9bp9PhxIkTAd8vQRAEoQ2JKCJoDBo0CIcOHXK+fu2119CpU6fQdegiZt26dejcuTOioqLQsmVLLFiwQLN9RUUFhg0bhvbt28NoNGLAgAGa7f/8808YjUbF/+/p06fx8MMP45JLLkFMTAzat2+PrVu3+v9mCIK4OLDbAXN5qHuhCYkoImjExMSgYcOGoe7GRc/x48fRr18/3Hzzzdi5cydGjx6Nxx9/HL/++qvqNlarFTExMXj66aeRmpqquf+CggI8+uijuPXWW93WXbhwATfccAMiIiLwyy+/YN++fZg2bRrq1atX5fdFEEQt54t7gDeTgZJzoe6JKiSiwgS73Y4ykyUkf3a73as+rlixAomJic7yDDt37oROp8NLL73kbPP444/j4YcfBsCH8xYsWIBJkyZh165d0Ol00Ol0nBuSl5eHe+65B7GxsWjVqhV+/PFHnz6/Tz75BImJiVi9ejXmzZuHxo0bu1WC79+/Px577DHVfRQUFOA///kPkpKSEB0djXbt2mHFihUAgPPnz2Pw4MFo0qQJYmNj0b59e3z55Zfc9jabDVOnTkXLli0RFRWFyy+/HG+++SbX5tixY7j55psRGxuLjh07IjMzk1u/ceNG3HjjjYiJiUHTpk3x9NNPo7S01KfPQmTu3Llo3rw5pk2bhjZt2iA9PR33338/3n//fdVt4uLi8NFHH2HkyJFITk7W3P8TTzyBhx56CD169HBb984776Bp06b47LPP0K1bNzRv3hy33347WrRoUaX3RBDERcCxtdLjvuUh7YYWVGwzTCg3W9F2grozEEz2TU5DbKTnU+HGG29EcXExduzYga5du2L9+vVo0KAB1q1b52yzfv16vPjii27bDho0CHv27EFGRgZ+//13AEBCQoJz/aRJkzB16lS8++67+OCDDzBkyBCcPHkS9evX99ivqVOnYurUqfjtt9/QrVs3dO7cGU899RTWrl3rdEfy8/ORkZGBlStXKu7DZrPhjjvuQHFxMRYtWoQWLVpg3759zqKMFRUV6NKlC1588UXEx8fj559/xiOPPIIWLVqgW7duAIBx48bh448/xvvvv49evXrh7NmzOHDgAHecV155Be+99x5atWqFV155BYMHD8aRI0dgNBpx9OhR9OnTB2+88Qbmz5+Pc+fOIT09Henp6fjss888fg5qZGZmurlJaWlpGD16tN/7lPnss89w7NgxLFq0CG+88Ybb+h9//BFpaWkYOHAg1q9fjyZNmuC///0vRo4cWeVjEwRxkWAP36nRSEQRXpOQkIBOnTph3bp16Nq1K9atW4dnn30WkyZNQklJCQoLC3HkyBGkpKS4bRsTE4M6derAaDQqOhvDhg3D4MGDAQBvvfUWZs2ahS1btqBPnz6afXrxxRfxxRdfYP369bjmmmsAAPXq1cMd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ix07dmDkyJGIi4vD0KFDvfoMReTBIePGjXMu8zQ4RMRut2PNmjU4ePAg3nnnHedym82GRx55BC+88AKuueYa1e3ffvttvP7667j88svx0EMP4dlnn4XRSBMxEES1Ya30fZtlw4Cjq4HOjwJ3fwDsWCQJpE6DXW0URZSuSl0NBiG92gwaNAjnzp3DhAkTkJ2djU6dOiEjI8OZGJ6VlQW93mWW9ezZE0uWLMH48ePx8ssvo1WrVli+fDnatWvnbDN27FiUlpZi1KhRKCgoQK9evZCRkcENaZ4wYQI+//xz5+trr70WALB27Vr07t0bALBw4UI8++yz6NevH/R6PVJSUpCRkaH6i/hiICEhAZ06dcK6devQtWtXrFu3Ds8++ywmTZqEkpISFBYW4siRI0hJSXHbNiYmBnXq1IHRaFQMrQ4bNgyDB0tfoLfeeguzZs3Cli1b0KdPH3z44Ydo2rQpZs+eDZ1Oh9atW+PMmTN48cUXMWHCBO4cUePSSy8FACQmJnoV2i0pKUG/fv1QWVmJtWvXOgVfWloa4uLi8P333+ORRx4BACxZsgR333036tatq7ivOXPmICEhAUuXLnWeP1dddZVz/S233MK1nzdvHhITE7F+/Xrceeed+P3337Flyxbs37/fud2VV17JbWM2mzF37ly0aNECAJCeno7Jkyc710+cOBHTpk3DvffeC0Bybfft24f/+7//81tEaQ0OOXDggOa2hYWFaNKkCSorK2EwGPDhhx/itttuc65/5513YDQa8fTTT6vu4+mnn0bnzp1Rv359bNq0CePGjcPZs2cxffp0v94PQRDVxFFHtGj7QiB1EvCDo5B12/5AZKz0nBNRjmhJGIbzQv6TLT09Henp6YrrWIdDZuDAgRg4cKDq/nQ6HSZPnszdQEQWLFjAhZKUiI+Px6effopPP/1Us13AiIiVHKFQEBHrddOUlBSsW7cOzz33HDZs2IApU6bg66+/xsaNG5Gfn4/GjRujVatWPnehQ4cOzudxcXGIj4935tXs378fPXr0gI75FXLDDTegpKQE//77Ly6//HKfj+eJwYMH47LLLsOaNWsQExPjXG40GvHAAw9g8eLFeOSRR1BaWooffvgBS5cuBQDccccd2LBhAwDgiiuuwN69e7Fz507ceOONqgI8JycH48ePx7p165Cbmwur1YqysjJn/t3OnTtx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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "device = \"cuda:2\"\n",
    "\n",
    "sa = SelfAttention(1024, 8).to(device)\n",
    "\n",
    "num_tokens = 512\n",
    "use_kv_cache = True\n",
    "time_per_step = []\n",
    "\n",
    "\n",
    "def generate(num_tokens, use_kv_cache):\n",
    "    time_per_step = []\n",
    "    current_result = torch.randn(32, num_tokens, 1024).to(device)\n",
    "\n",
    "    for token_index in trange(num_tokens):\n",
    "        torch.cuda.synchronize()\n",
    "        start_t = time.perf_counter()\n",
    "\n",
    "        with torch.inference_mode():\n",
    "            if use_kv_cache:\n",
    "                res = sa(current_result[:, token_index : token_index + 1 ], start_pos=token_index, use_cache=True)\n",
    "                current_result[:, token_index + 1:token_index + 2] = res\n",
    "            else:\n",
    "                res = sa(current_result[:, :token_index + 1], use_cache=False)\n",
    "                current_result[:, token_index + 1:token_index + 2] = res[:, -1].unsqueeze(dim=1)\n",
    "    \n",
    "        torch.cuda.synchronize()\n",
    "        ellapsed_t = time.perf_counter() - start_t\n",
    "        time_per_step.append(ellapsed_t)\n",
    "    return time_per_step\n",
    "\n",
    "warmup = generate(num_tokens, use_kv_cache=True)\n",
    "total_time_with_kv_cache = generate(num_tokens, use_kv_cache=True)\n",
    "warmup = generate(num_tokens, use_kv_cache=False)\n",
    "total_time_without_kv_cache = generate(num_tokens, use_kv_cache=False)\n",
    "\n",
    "plt.plot(total_time_with_kv_cache, label=f\"with kv-cache | {sum(total_time_with_kv_cache):.3f}\")\n",
    "plt.plot(total_time_without_kv_cache, label=f\"without kv-cache | {sum(total_time_without_kv_cache):.3f}\")\n",
    "plt.xlabel(\"Num tokens\")\n",
    "plt.ylabel(\"Seconds\")\n",
    "plt.legend();\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Triton\n",
    "\n",
    "The aim of the Triton framework, developed by OpenAI, is to simplify and optimize the development of highly efficient GPU-based computations for deep learning models. It provides a Python-like programming environment that allows researchers and developers to write highly parallel and performant custom operations more easily than traditional GPU programming approaches, such as CUDA. Triton aims to make it more accessible for developers to leverage GPU acceleration, ultimately facilitating the development of faster and more efficient deep learning models.\n",
    "\n",
    "The examples are taken from https://triton-lang.org/main/index.html, so feel free to read the documentation yourself.\n",
    "\n",
    "Why do we need Triton?\n",
    "1) Easier to write an efficient kernel\n",
    "2) Automated work with memory and shared memory\n",
    "\n",
    "Note: Both, CUDA and Triton compile into PTX.\n",
    "\n",
    "Why do we still need CUDA then?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Vector Addition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import os\n",
    "# os.environ[\"TRITON_INTERPRET\"] = \"1\"\n",
    "\n",
    "import triton\n",
    "import triton.language as tl"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "@triton.jit\n",
    "def add_kernel(\n",
    "    x_ptr,  # *Pointer* to first input vector.\n",
    "    y_ptr,  # *Pointer* to second input vector.\n",
    "    output_ptr,  # *Pointer* to output vector.\n",
    "\n",
    "    n_elements,  # Size of the vector.\n",
    "    BLOCK_SIZE: tl.constexpr,  # Number of elements each program should process.\n",
    "    # NOTE: `constexpr` so it can be used as a shape value.\n",
    "):\n",
    "    # There are multiple 'programs' processing different data. We identify which program\n",
    "    # we are here:\n",
    "\n",
    "    pid = tl.program_id(axis=0)  # We use a 1D launch grid so axis is 0.\n",
    "\n",
    "    # This program will process inputs that are offset from the initial data.\n",
    "    # For instance, if you had a vector of length 256 and block_size of 64, the programs\n",
    "    # would each access the elements [0:64, 64:128, 128:192, 192:256].\n",
    "    # Note that offsets is a list of pointers:\n",
    "    block_start = pid * BLOCK_SIZE\n",
    "    offsets = block_start + tl.arange(0, BLOCK_SIZE)\n",
    "\n",
    "    # NOTE\n",
    "    # tl.device_print('arange', tl.arange(0, BLOCK_SIZE))\n",
    "\n",
    "    # Create a mask to guard memory operations against out-of-bounds accesses.\n",
    "    mask = offsets < n_elements\n",
    "    # Load x and y from DRAM, masking out any extra elements in case the input is not a\n",
    "    # multiple of the block size.\n",
    "\n",
    "    x = tl.load(x_ptr + offsets, mask=mask)\n",
    "    y = tl.load(y_ptr + offsets, mask=mask)\n",
    "    output = x + y\n",
    "\n",
    "    # Write x + y back to DRAM.\n",
    "    tl.store(output_ptr + offsets, output, mask=mask)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(x: torch.Tensor, y: torch.Tensor):\n",
    "    # We need to preallocate the output.\n",
    "    output = torch.empty_like(x)\n",
    "    # assert x.is_cuda and y.is_cuda and output.is_cuda\n",
    "    n_elements = output.numel()\n",
    "\n",
    "    # The SPMD (single program, multiple data) launch grid denotes the number of kernel instances that run in parallel.\n",
    "    # It is analogous to CUDA launch grids. It can be either Tuple[int], or Callable(metaparameters) -> Tuple[int].\n",
    "    # In this case, we use a 1D grid where the size is the number of blocks:\n",
    "    grid = lambda meta: (triton.cdiv(n_elements, meta['BLOCK_SIZE']), )\n",
    "\n",
    "    # NOTE:\n",
    "    #  - Each torch.tensor object is implicitly converted into a pointer to its first element.\n",
    "    #  - `triton.jit`'ed functions can be indexed with a launch grid to obtain a callable GPU kernel.\n",
    "    #  - Don't forget to pass meta-parameters as keywords arguments.\n",
    "\n",
    "    add_kernel[grid](x, y, output, n_elements, BLOCK_SIZE=1024)\n",
    "    \n",
    "    # We return a handle to z but, since `torch.cuda.synchronize()` hasn't been called, the kernel is still\n",
    "    # running asynchronously at this point.\n",
    "    return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1.3713, 1.3076, 0.4940,  ..., 0.6724, 1.2141, 0.9733], device='cuda:0')\n",
      "tensor([1.3713, 1.3076, 0.4940,  ..., 0.6724, 1.2141, 0.9733], device='cuda:0')\n",
      "The maximum difference between torch and triton is 0.0\n"
     ]
    }
   ],
   "source": [
    "torch.manual_seed(0)\n",
    "size = 98432\n",
    "x = torch.rand(size, device=\"cuda\")\n",
    "y = torch.rand(size, device=\"cuda\")\n",
    "output_torch = x + y\n",
    "output_triton = add(x, y)\n",
    "print(output_torch)\n",
    "print(output_triton)\n",
    "print(f'The maximum difference between torch and triton is '\n",
    "      f'{torch.max(torch.abs(output_torch - output_triton))}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What is a stride in memory formats?\n",
    "\n",
    "In memory formats, a stride is a term that describes the step size or the distance (measured in elements or bytes) between consecutive elements of a data structure (such as an array or a tensor) along a particular dimension when laid out in memory. Strides are crucial for efficiently accessing multi-dimensional data structures, especially when dealing with operations that require reshaping, slicing, or broadcasting of data without physically rearranging it in memory.\n",
    "\n",
    "For instance, consider a 2D array (matrix) stored in row-major order (common in C and Python numpy arrays), where all elements of a row are stored in contiguous memory locations. The stride along the row dimension (often called the \"row stride\") would be 1, indicating that elements along a row are adjacent in memory. The stride along the column dimension (the \"column stride\"), however, would be equal to the number of columns in the array, as one has to skip all elements of a row to move to the next element in a column.\n",
    "\n",
    "By manipulating strides, software libraries can implement operations like transposition, slicing, and various tensor manipulations efficiently, allowing for complex data manipulations without the need for expensive memory copies. Strides are a fundamental concept in libraries that handle multi-dimensional data structures, ensuring data is accessed and manipulated efficiently in memory.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![rowmajorcolmajor](./images/rowcolumnarrays.webp)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.backends.xnnpack\n",
    "\n",
    "print(\"XNNPACK is enabled: \", torch.backends.xnnpack.enabled, \"\\n\")\n",
    "\n",
    "N, C, H, W = 2, 3, 200, 200\n",
    "x = torch.rand(N, C, H, W)\n",
    "print(\"Contiguous shape: \", x.shape)\n",
    "print(\"Contiguous stride: \", x.stride())\n",
    "print()\n",
    "\n",
    "xcl = x.to(memory_format=torch.channels_last)\n",
    "print(\"Channels-Last shape: \", xcl.shape)\n",
    "print(\"Channels-Last stride: \", xcl.stride())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## MatMul"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![image](https://triton-lang.org/main/_images/grouped_vs_row_major_ordering.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# `triton.jit`'ed functions can be auto-tuned by using the `triton.autotune` decorator, which consumes:\n",
    "#   - A list of `triton.Config` objects that define different configurations of\n",
    "#       meta-parameters (e.g., `BLOCK_SIZE_M`) and compilation options (e.g., `num_warps`) to try\n",
    "#   - An auto-tuning *key* whose change in values will trigger evaluation of all the\n",
    "#       provided configs\n",
    "@triton.autotune(\n",
    "    configs=[\n",
    "        triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 64, 'GROUP_SIZE_M': 8}, num_stages=3,\n",
    "                      num_warps=8),\n",
    "        triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4,\n",
    "                      num_warps=4),\n",
    "        triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4,\n",
    "                      num_warps=4),\n",
    "        triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4,\n",
    "                      num_warps=4),\n",
    "        triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4,\n",
    "                      num_warps=4),\n",
    "        triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4,\n",
    "                      num_warps=4),\n",
    "        triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5,\n",
    "                      num_warps=2),\n",
    "        triton.Config({'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5,\n",
    "                      num_warps=2),\n",
    "    ],\n",
    "    key=['M', 'N', 'K'],\n",
    ")\n",
    "@triton.jit\n",
    "def matmul_kernel(\n",
    "        # Pointers to matrices\n",
    "        a_ptr, b_ptr, c_ptr,\n",
    "        # Matrix dimensions\n",
    "        M, N, K,\n",
    "        # The stride variables represent how much to increase the ptr by when moving by 1\n",
    "        # element in a particular dimension. E.g. `stride_am` is how much to increase `a_ptr`\n",
    "        # by to get the element one row down (A has M rows).\n",
    "        stride_am, stride_ak,  #\n",
    "        stride_bk, stride_bn,  #\n",
    "        stride_cm, stride_cn,\n",
    "        # Meta-parameters\n",
    "        BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr, BLOCK_SIZE_K: tl.constexpr,  #\n",
    "        GROUP_SIZE_M: tl.constexpr,  #\n",
    "        ACTIVATION: tl.constexpr  #\n",
    "):\n",
    "    \"\"\"Kernel for computing the matmul C = A x B.\n",
    "    A has shape (M, K), B has shape (K, N) and C has shape (M, N)\n",
    "    \"\"\"\n",
    "    # -----------------------------------------------------------\n",
    "    # Map program ids `pid` to the block of C it should compute.\n",
    "    # This is done in a grouped ordering to promote L2 data reuse.\n",
    "    # See above `L2 Cache Optimizations` section for details.\n",
    "    pid = tl.program_id(axis=0)\n",
    "    \n",
    "    num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)\n",
    "    num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)\n",
    "    num_pid_in_group = GROUP_SIZE_M * num_pid_n\n",
    "    group_id = pid // num_pid_in_group\n",
    "    first_pid_m = group_id * GROUP_SIZE_M\n",
    "    group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)\n",
    "\n",
    "    pid_m = first_pid_m + (pid % group_size_m)\n",
    "    pid_n = (pid % num_pid_in_group) // group_size_m\n",
    "\n",
    "    # ----------------------------------------------------------\n",
    "    # Create pointers for the first blocks of A and B.\n",
    "    # We will advance this pointer as we move in the K direction\n",
    "    # and accumulate\n",
    "    # `a_ptrs` is a block of [BLOCK_SIZE_M, BLOCK_SIZE_K] pointers\n",
    "    # `b_ptrs` is a block of [BLOCK_SIZE_K, BLOCK_SIZE_N] pointers\n",
    "    # See above `Pointer Arithmetic` section for details\n",
    "    offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M\n",
    "    offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N\n",
    "    offs_k = tl.arange(0, BLOCK_SIZE_K)\n",
    "    a_ptrs = a_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)\n",
    "    b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)\n",
    "\n",
    "    # -----------------------------------------------------------\n",
    "    # Iterate to compute a block of the C matrix.\n",
    "    # We accumulate into a `[BLOCK_SIZE_M, BLOCK_SIZE_N]` block\n",
    "    # of fp32 values for higher accuracy.\n",
    "    # `accumulator` will be converted back to fp16 after the loop.\n",
    "    accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)\n",
    "    for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):\n",
    "        # Load the next block of A and B, generate a mask by checking the K dimension.\n",
    "        # If it is out of bounds, set it to 0.\n",
    "        a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0)\n",
    "        b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)\n",
    "        # We accumulate along the K dimension.\n",
    "        accumulator += tl.dot(a, b)\n",
    "        # Advance the ptrs to the next K block.\n",
    "        a_ptrs += BLOCK_SIZE_K * stride_ak\n",
    "        b_ptrs += BLOCK_SIZE_K * stride_bk\n",
    "    # You can fuse arbitrary activation functions here\n",
    "    # while the accumulator is still in FP32!\n",
    "    \n",
    "    if ACTIVATION == \"leaky_relu\":\n",
    "        accumulator = leaky_relu(accumulator)\n",
    "    c = accumulator.to(tl.float16)\n",
    "\n",
    "    # -----------------------------------------------------------\n",
    "    # Write back the block of the output matrix C with masks.\n",
    "    offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)\n",
    "    offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)\n",
    "    c_ptrs = c_ptr + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]\n",
    "    c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)\n",
    "    tl.store(c_ptrs, c, mask=c_mask)\n",
    "\n",
    "\n",
    "# We can fuse `leaky_relu` by providing it as an `ACTIVATION` meta-parameter in `_matmul`.\n",
    "@triton.jit\n",
    "def leaky_relu(x):\n",
    "    x = x + 1\n",
    "    return tl.where(x >= 0, x, 0.01 * x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "def matmul(a, b, activation=\"\", verbose=False):\n",
    "    # Check constraints.\n",
    "    assert a.shape[1] == b.shape[0], \"Incompatible dimensions\"\n",
    "    assert a.is_contiguous(), \"Matrix A must be contiguous\"\n",
    "    assert b.is_contiguous(), \"Matrix B must be contiguous\"\n",
    "    M, K = a.shape\n",
    "    K, N = b.shape\n",
    "\n",
    "    # Allocates output.\n",
    "    c = torch.empty((M, N), device=a.device, dtype=a.dtype)\n",
    "\n",
    "    if verbose:\n",
    "        print(f\"{M=} {K=} {N=}\")\n",
    "        print(f\"{a.stride(0)=}, {a.stride(1)=}\")\n",
    "        print(f\"{b.stride(0)=}, {b.stride(1)=}\")\n",
    "        print(f\"{c.stride(0)=}, {c.stride(1)=}\")\n",
    "\n",
    "    # 1D launch kernel where each block gets its own program.\n",
    "    grid = lambda META: (triton.cdiv(M, META['BLOCK_SIZE_M']) * triton.cdiv(N, META['BLOCK_SIZE_N']), )\n",
    "    matmul_kernel[grid](\n",
    "        a, b, c,  #\n",
    "        M, N, K,  #\n",
    "        a.stride(0), a.stride(1),  #\n",
    "        b.stride(0), b.stride(1),  #\n",
    "        c.stride(0), c.stride(1),  #\n",
    "        ACTIVATION=activation  #\n",
    "    )\n",
    "    return c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = torch.randn((512, 512), device='cuda', dtype=torch.float16)\n",
    "b = torch.randn((512, 512), device='cuda', dtype=torch.float16)\n",
    "triton_output = matmul(a, b, activation=\"\")\n",
    "output = a @ b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "triton_output - output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "@triton.testing.perf_report(\n",
    "    triton.testing.Benchmark(\n",
    "        x_names=['M', 'N', 'K'],  # Argument names to use as an x-axis for the plot\n",
    "        x_vals=[128 * i for i in range(2, 33)],  # Different possible values for `x_name`\n",
    "        line_arg='provider',  # Argument name whose value corresponds to a different line in the plot\n",
    "        # Possible values for `line_arg`\n",
    "        line_vals=['cublas', 'triton'],\n",
    "        # Label name for the lines\n",
    "        line_names=[\"cuBLAS\", \"Triton\"],\n",
    "        # Line styles\n",
    "        styles=[('green', '-'), ('blue', '-')],\n",
    "        ylabel=\"TFLOPS\",  # Label name for the y-axis\n",
    "        plot_name=\"matmul-performance\",  # Name for the plot, used also as a file name for saving the plot.\n",
    "        args={},\n",
    "    ))\n",
    "def benchmark(M, N, K, provider):\n",
    "    a = torch.randn((M, K), device='cuda', dtype=torch.float16)\n",
    "    b = torch.randn((K, N), device='cuda', dtype=torch.float16)\n",
    "    quantiles = [0.5, 0.2, 0.8]\n",
    "    if provider == 'cublas':\n",
    "        ms, min_ms, max_ms = triton.testing.do_bench(lambda: torch.matmul(a, b), quantiles=quantiles)\n",
    "    if provider == 'triton':\n",
    "        ms, min_ms, max_ms = triton.testing.do_bench(lambda: matmul(a, b), quantiles=quantiles)\n",
    "    perf = lambda ms: 2 * M * N * K * 1e-12 / (ms * 1e-3)\n",
    "    return perf(ms), perf(max_ms), perf(min_ms)\n",
    "\n",
    "\n",
    "benchmark.run(show_plots=True, print_data=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Fusing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p float=\"left\">\n",
    "<img src=\"./images/fused_kernels1.png\" width=\"400\"/>\n",
    "<img src=\"./images/fused_kernels2.png\" width=\"400\"/>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Projections Fusing\n",
    "\n",
    "This technique aims to reduce the number of operations. Instead of 3 matrix multiplications (projections on q, k and v) \n",
    "we will have one big one, due to which we will reduce the number of calls and memory moves to GPU registers.\n",
    "\n",
    "The main work will take place in the `fuse_qkv` function, which should be called once before using the layer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DummySelfAttention(nn.Module):\n",
    "    def __init__(self, embed_size, heads):\n",
    "        super().__init__()\n",
    "        self.embed_size = embed_size\n",
    "        self.heads = heads\n",
    "        self.head_dim = embed_size // heads\n",
    "\n",
    "        assert self.head_dim * heads == embed_size, \"Embed size needs to be divisible by heads\"\n",
    "\n",
    "        # Linear layers to generate Query, Key, and Value matrices\n",
    "        self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)\n",
    "        self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)\n",
    "        self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)\n",
    "\n",
    "        self.fc_out = nn.Linear(heads * self.head_dim, embed_size)\n",
    "\n",
    "        self.fused_qkv_projection = None\n",
    "\n",
    "    def fuse_qkv(self):\n",
    "        @torch.no_grad()\n",
    "        def fuse_projections(*projections: Sequence[nn.Linear]) -> nn.Linear:\n",
    "            concatenated_weights = torch.cat([p.weight.data for p in projections])\n",
    "            device = concatenated_weights.device\n",
    "            dtype = concatenated_weights.dtype\n",
    "            in_features = concatenated_weights.shape[1]\n",
    "            out_features = concatenated_weights.shape[0]\n",
    "            projection = nn.Linear(in_features, out_features, bias=False, device=device, dtype=dtype)\n",
    "            projection.weight.copy_(concatenated_weights)\n",
    "            return projection\n",
    "        \n",
    "        self.fused_qkv_projection = fuse_projections(self.queries, self.keys, self.values)\n",
    "        del self.queries, self.keys, self.values\n",
    "\n",
    "    def forward(self, input: torch.Tensor, kv_cache=None) -> torch.Tensor:\n",
    "        batch_size = input.shape[0]\n",
    "        input_len = input.shape[1]\n",
    "\n",
    "        # Split the embedding into self.heads pieces\n",
    "        input = input.reshape(batch_size, input_len, self.heads, self.head_dim)\n",
    "\n",
    "        if self.fused_qkv_projection is None:\n",
    "            q = self.queries(input)\n",
    "            k = self.keys(input)\n",
    "            v = self.values(input)\n",
    "        else:\n",
    "            q, k, v = self.fused_qkv_projection(input).chunk(chunks=3, dim=-1)\n",
    "\n",
    "        # Scaled Dot-Product Attention\n",
    "        energy = torch.einsum(\"nqhd,nkhd->nhqk\", [q, k])\n",
    "        attention = torch.softmax(energy / (self.embed_size ** (1 / 2)), dim=3)\n",
    "        out = torch.einsum(\"nhql,nlhd->nqhd\", [attention, v]).reshape(batch_size, input_len, self.heads*self.head_dim)\n",
    "        return self.fc_out(out)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class ProfilerConfig:\n",
    "    scheduler: Optional[Callable[[int], int]] = field(default=None, metadata={\"omegaconf_ignore\": True})\n",
    "    num_steps: int = field(init=False)\n",
    "    amp: bool = False\n",
    "\n",
    "    def __post_init__(self):\n",
    "        if self.scheduler is None:\n",
    "            self.scheduler = {\"skip_first\": 2, \"wait\": 2, \"warmup\": 3, \"active\": 3, \"repeat\": 0}\n",
    "\n",
    "        self.num_steps = sum([v for k, v in self.scheduler.items() if k != \"repeat\"])\n",
    "        self.scheduler = schedule(**self.scheduler)\n",
    "\n",
    "@torch.inference_mode()\n",
    "def profile_model(model: nn.Module, input: Dict[str, torch.Tensor], config: ProfilerConfig):\n",
    "    training_state = model.training\n",
    "    model.eval()\n",
    "\n",
    "    with autocast(dtype=torch.float16, device_type=\"cuda\", enabled=config.amp), profile(\n",
    "        activities=[ProfilerActivity.CUDA ],\n",
    "        schedule=config.scheduler\n",
    "    ) as p:\n",
    "        for _ in range(config.num_steps):\n",
    "            _ = model(**input)\n",
    "            p.step()\n",
    "\n",
    "    model.train(training_state)\n",
    "    return p"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                                   Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                 cudaDeviceGetAttribute         1.54%       5.454us         1.54%       5.454us       0.364us       0.000us         0.00%       0.000us       0.000us            15  \n",
      "                                         cuLaunchKernel        41.04%     145.657us        41.04%     145.657us       9.710us       0.000us         0.00%       0.000us       0.000us            15  \n",
      "void cutlass::Kernel2<cutlass_80_tensorop_s1688gemm_...         0.00%       0.000us         0.00%       0.000us       0.000us     138.236us        21.15%     138.236us      15.360us             9  \n",
      "                                       cudaLaunchKernel        39.63%     140.635us        39.63%     140.635us       7.813us       0.000us         0.00%       0.000us       0.000us            18  \n",
      "void at::native::elementwise_kernel<128, 2, at::nati...         0.00%       0.000us         0.00%       0.000us       0.000us     262.943us        40.22%     262.943us      21.912us            12  \n",
      "void cutlass::Kernel2<cutlass_80_tensorop_s1688gemm_...         0.00%       0.000us         0.00%       0.000us       0.000us     104.672us        16.01%     104.672us      17.445us             6  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us      24.000us         3.67%      24.000us       8.000us             3  \n",
      "void (anonymous namespace)::softmax_warp_forward<flo...         0.00%       0.000us         0.00%       0.000us       0.000us      32.896us         5.03%      32.896us      10.965us             3  \n",
      "                                  cudaFuncGetAttributes         4.74%      16.814us         4.74%      16.814us       2.802us       0.000us         0.00%       0.000us       0.000us             6  \n",
      "                                    cudaLaunchKernelExC         8.17%      29.006us         8.17%      29.006us       9.669us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "sm90_xmma_gemm_f32f32_tf32f32_f32_tn_n_tilesize128x1...         0.00%       0.000us         0.00%       0.000us       0.000us      90.945us        13.91%      90.945us      30.315us             3  \n",
      "                                  cudaDeviceSynchronize         4.88%      17.316us         4.88%      17.316us      17.316us       0.000us         0.00%       0.000us       0.000us             1  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "Self CPU time total: 354.882us\n",
      "Self CUDA time total: 653.692us\n",
      "\n"
     ]
    }
   ],
   "source": [
    "sa = DummySelfAttention(1024, 8).cuda()\n",
    "sa_input = torch.randn(32, 128, 1024).cuda()\n",
    "\n",
    "p = profile_model(sa, {'input': sa_input}, ProfilerConfig(amp=False))\n",
    "print(p.key_averages())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                                   Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                        cudaMemsetAsync         5.54%      15.886us         5.54%      15.886us       5.295us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                  cudaFuncGetAttributes         8.26%      23.698us         8.26%      23.698us       1.975us       0.000us         0.00%       0.000us       0.000us            12  \n",
      "                                        Memset (Device)         0.00%       0.000us         0.00%       0.000us       0.000us       2.273us         0.38%       2.273us       0.758us             3  \n",
      "                                    cudaLaunchKernelExC        15.74%      45.159us        15.74%      45.159us       7.527us       0.000us         0.00%       0.000us       0.000us             6  \n",
      "sm90_xmma_gemm_f32f32_tf32f32_f32_tn_n_tilesize128x1...         0.00%       0.000us         0.00%       0.000us       0.000us     172.384us        28.84%     172.384us      28.731us             6  \n",
      "                                       cudaLaunchKernel        51.61%     148.037us        51.61%     148.037us       8.224us       0.000us         0.00%       0.000us       0.000us            18  \n",
      "void at::native::elementwise_kernel<128, 2, at::nati...         0.00%       0.000us         0.00%       0.000us       0.000us     260.928us        43.66%     260.928us      21.744us            12  \n",
      "                                 cudaDeviceGetAttribute         0.76%       2.186us         0.76%       2.186us       0.364us       0.000us         0.00%       0.000us       0.000us             6  \n",
      "                                         cuLaunchKernel        13.58%      38.962us        13.58%      38.962us       6.494us       0.000us         0.00%       0.000us       0.000us             6  \n",
      "void cutlass::Kernel2<cutlass_80_tensorop_s1688gemm_...         0.00%       0.000us         0.00%       0.000us       0.000us     104.321us        17.45%     104.321us      17.387us             6  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us      25.152us         4.21%      25.152us       8.384us             3  \n",
      "void (anonymous namespace)::softmax_warp_forward<flo...         0.00%       0.000us         0.00%       0.000us       0.000us      32.640us         5.46%      32.640us      10.880us             3  \n",
      "                                  cudaDeviceSynchronize         4.50%      12.893us         4.50%      12.893us      12.893us       0.000us         0.00%       0.000us       0.000us             1  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "Self CPU time total: 286.821us\n",
      "Self CUDA time total: 597.698us\n",
      "\n"
     ]
    }
   ],
   "source": [
    "sa_fused = DummySelfAttention(1024, 8).cuda()\n",
    "sa_fused.load_state_dict(sa.state_dict())\n",
    "sa_fused.fuse_qkv()\n",
    "\n",
    "p = profile_model(sa_fused, {'input': sa_input}, ProfilerConfig(amp=False))\n",
    "print(p.key_averages())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.testing.assert_close(sa(sa_input), sa_fused(sa_input))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see the result of work with and without fusing is identical, but at the same time we see a 7-10% increase in timing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Large scale\n",
    "\n",
    "For larger nets, the absolute value of acceleration will be larger"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                                   Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                 cudaDeviceGetAttribute         1.43%      37.130us         1.43%      37.130us       0.248us       0.000us         0.00%       0.000us       0.000us           150  \n",
      "                                         cuLaunchKernel        37.54%     976.090us        37.54%     976.090us       6.507us       0.000us         0.00%       0.000us       0.000us           150  \n",
      "void cutlass::Kernel2<cutlass_80_tensorop_s1688gemm_...         0.00%       0.000us         0.00%       0.000us       0.000us       1.284ms        19.83%       1.284ms      14.263us            90  \n",
      "                                       cudaLaunchKernel        46.69%       1.214ms        46.69%       1.214ms       6.745us       0.000us         0.00%       0.000us       0.000us           180  \n",
      "void at::native::elementwise_kernel<128, 2, at::nati...         0.00%       0.000us         0.00%       0.000us       0.000us       2.648ms        40.89%       2.648ms      22.064us           120  \n",
      "void cutlass::Kernel2<cutlass_80_tensorop_s1688gemm_...         0.00%       0.000us         0.00%       0.000us       0.000us       1.062ms        16.40%       1.062ms      17.694us            60  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us     255.297us         3.94%     255.297us       8.510us            30  \n",
      "void (anonymous namespace)::softmax_warp_forward<flo...         0.00%       0.000us         0.00%       0.000us       0.000us     326.940us         5.05%     326.940us      10.898us            30  \n",
      "                                  cudaFuncGetAttributes         5.06%     131.472us         5.06%     131.472us       2.191us       0.000us         0.00%       0.000us       0.000us            60  \n",
      "                                    cudaLaunchKernelExC         8.38%     217.805us         8.38%     217.805us       7.260us       0.000us         0.00%       0.000us       0.000us            30  \n",
      "sm90_xmma_gemm_f32f32_tf32f32_f32_tn_n_tilesize128x1...         0.00%       0.000us         0.00%       0.000us       0.000us     899.261us        13.89%     899.261us      29.975us            30  \n",
      "                                  cudaDeviceSynchronize         0.91%      23.644us         0.91%      23.644us      23.644us       0.000us         0.00%       0.000us       0.000us             1  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "Self CPU time total: 2.600ms\n",
      "Self CUDA time total: 6.475ms\n",
      "\n"
     ]
    }
   ],
   "source": [
    "sa_large = nn.Sequential(*[DummySelfAttention(1024, 8)] * 10).cuda()\n",
    "\n",
    "p = profile_model(sa_large, {'input': sa_input}, ProfilerConfig(amp=False))\n",
    "print(p.key_averages())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                                   Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                        cudaMemsetAsync         3.56%     128.696us         3.56%     128.696us       4.290us       0.000us         0.00%       0.000us       0.000us            30  \n",
      "                                  cudaFuncGetAttributes         6.75%     243.670us         6.75%     243.670us       2.031us       0.000us         0.00%       0.000us       0.000us           120  \n",
      "                                        Memset (Device)         0.00%       0.000us         0.00%       0.000us       0.000us      23.105us         0.39%      23.105us       0.770us            30  \n",
      "                                    cudaLaunchKernelExC        11.98%     432.570us        11.98%     432.570us       7.210us       0.000us         0.00%       0.000us       0.000us            60  \n",
      "sm90_xmma_gemm_f32f32_tf32f32_f32_tn_n_tilesize128x1...         0.00%       0.000us         0.00%       0.000us       0.000us       1.727ms        28.83%       1.727ms      28.781us            60  \n",
      "                                       cudaLaunchKernel        65.98%       2.382ms        65.98%       2.382ms      13.234us       0.000us         0.00%       0.000us       0.000us           180  \n",
      "void at::native::elementwise_kernel<128, 2, at::nati...         0.00%       0.000us         0.00%       0.000us       0.000us       2.613ms        43.62%       2.613ms      21.778us           120  \n",
      "                                 cudaDeviceGetAttribute         0.61%      22.136us         0.61%      22.136us       0.369us       0.000us         0.00%       0.000us       0.000us            60  \n",
      "                                         cuLaunchKernel        10.56%     381.154us        10.56%     381.154us       6.353us       0.000us         0.00%       0.000us       0.000us            60  \n",
      "void cutlass::Kernel2<cutlass_80_tensorop_s1688gemm_...         0.00%       0.000us         0.00%       0.000us       0.000us       1.051ms        17.55%       1.051ms      17.522us            60  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us     249.249us         4.16%     249.249us       8.308us            30  \n",
      "void (anonymous namespace)::softmax_warp_forward<flo...         0.00%       0.000us         0.00%       0.000us       0.000us     326.909us         5.46%     326.909us      10.897us            30  \n",
      "                                  cudaDeviceSynchronize         0.55%      20.014us         0.55%      20.014us      20.014us       0.000us         0.00%       0.000us       0.000us             1  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "Self CPU time total: 3.610ms\n",
      "Self CUDA time total: 5.991ms\n",
      "\n"
     ]
    }
   ],
   "source": [
    "sa_fused_large = nn.Sequential(*[DummySelfAttention(1024, 8)] * 10).cuda()\n",
    "for m in sa_fused_large.modules():\n",
    "    if isinstance(m, DummySelfAttention):\n",
    "        m.fuse_qkv()\n",
    "\n",
    "p = profile_model(sa_fused_large, {'input': sa_input}, ProfilerConfig(amp=False))\n",
    "print(p.key_averages())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Element-wise Fusing\n",
    "\n",
    "The following optimization is similar to the previous one. In modern DL there are many activations, which is a composition of simple operations, e.g. elementwise multiplication/addition, sigmoid/tangent.\n",
    "\n",
    "In case we naively implement such activations, each elementwise operation is performed by a separate kernel. That is, we load memory into it, perform the operation and return the memory back.\n",
    "Such memory management is very slow, so it is much better to perform all operations in a single kernel\n",
    "\n",
    "This fusion of operations can be achieved with the help of decorator `@torch.compile`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "@torch.compile\n",
    "def fused_geglu(x, gate):\n",
    "    \"\"\"\n",
    "    Gaussian error Gated Linear Units, GeGLU(x) = GeLU(x) * gate\n",
    "    from the paper https://arxiv.org/abs/2002.05202\n",
    "    \"\"\"\n",
    "    tanh = torch.tanh(0.79788456 * x * (1 + 0.044715 * x * x))\n",
    "    return gate * (x * 0.5 * (1.0 + tanh))\n",
    "\n",
    "\n",
    "class _FusedGeGLU(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, x: torch.Tensor, gate: torch.Tensor):\n",
    "        ctx.save_for_backward(x, gate)\n",
    "        return fused_geglu(x, gate)\n",
    "    \n",
    "\n",
    "def geglu(x, gate):\n",
    "    \"\"\"\n",
    "    Gaussian error Gated Linear Units, GeGLU(x) = GeLU(x) * gate\n",
    "    from the paper https://arxiv.org/abs/2002.05202\n",
    "    \"\"\"\n",
    "    tanh = torch.tanh(0.79788456 * x * (1 + 0.044715 * x * x))\n",
    "    return gate * (x * 0.5 * (1.0 + tanh))\n",
    "\n",
    "class _GeGLU(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, x: torch.Tensor, gate: torch.Tensor):\n",
    "        ctx.save_for_backward(x, gate)\n",
    "        return geglu(x, gate)\n",
    "\n",
    "\n",
    "class GeGLU(nn.Module):\n",
    "\n",
    "    def __init__(self, d_in: int, d_out: int, impl: str):\n",
    "        super().__init__()\n",
    "        # Combined linear projections $xW + b$ and $xV + c$\n",
    "        self.proj = nn.Linear(d_in, d_out * 2)\n",
    "        self._impl= impl\n",
    "\n",
    "    def forward(self, input: torch.Tensor):\n",
    "        x, gate = self.proj(input).chunk(2, dim=-1)\n",
    "        if self._impl == \"naive\":\n",
    "            return _GeGLU.apply(x, gate)\n",
    "        return _FusedGeGLU.apply(x, gate)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                                   Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                        cudaMemsetAsync         1.03%      15.567us         1.03%      15.567us       5.189us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                  cudaFuncGetAttributes         0.86%      13.038us         0.86%      13.038us       2.173us       0.000us         0.00%       0.000us       0.000us             6  \n",
      "                                    cudaLaunchKernelExC         1.72%      26.128us         1.72%      26.128us       8.709us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                       cudaLaunchKernel        11.48%     174.214us        11.48%     174.214us       5.807us       0.000us         0.00%       0.000us       0.000us            30  \n",
      "void at::native::elementwise_kernel<128, 2, at::nati...         0.00%       0.000us         0.00%       0.000us       0.000us     389.823us        20.80%     389.823us      32.485us            12  \n",
      "void at::native::elementwise_kernel<128, 2, at::nati...         0.00%       0.000us         0.00%       0.000us       0.000us     338.813us        18.07%     338.813us      42.352us             8  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us     172.255us         9.19%     172.255us      21.532us             8  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us     285.118us        15.21%     285.118us      35.640us             8  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us      90.111us         4.81%      90.111us      22.528us             4  \n",
      "                                        Memset (Device)         0.00%       0.000us         0.00%       0.000us       0.000us       3.071us         0.16%       3.071us       1.024us             3  \n",
      "sm90_xmma_gemm_f32f32_tf32f32_f32_tn_n_tilesize128x1...         0.00%       0.000us         0.00%       0.000us       0.000us     595.358us        31.76%     595.358us     198.453us             3  \n",
      "                                  cudaDeviceSynchronize        84.91%       1.289ms        84.91%       1.289ms       1.289ms       0.000us         0.00%       0.000us       0.000us             1  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "Self CPU time total: 1.518ms\n",
      "Self CUDA time total: 1.875ms\n",
      "\n"
     ]
    }
   ],
   "source": [
    "act = GeGLU(2048, 2048, impl=\"naive\").cuda()\n",
    "act_input = torch.randn(32, 128, 2048).cuda()\n",
    "\n",
    "p = profile_model(act, {'input': act_input}, ProfilerConfig(amp=False))\n",
    "print(p.key_averages())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                                   Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                        cudaMemsetAsync        10.15%      18.262us        10.15%      18.262us       6.087us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                  cudaFuncGetAttributes         7.04%      12.667us         7.04%      12.667us       2.111us       0.000us         0.00%       0.000us       0.000us             6  \n",
      "                                        Memset (Device)         0.00%       0.000us         0.00%       0.000us       0.000us       2.144us         0.30%       2.144us       0.715us             3  \n",
      "                                    cudaLaunchKernelExC        15.93%      28.665us        15.93%      28.665us       9.555us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "sm90_xmma_gemm_f32f32_tf32f32_f32_tn_n_tilesize128x1...         0.00%       0.000us         0.00%       0.000us       0.000us     608.254us        85.87%     608.254us     202.751us             3  \n",
      "                                         cuLaunchKernel        13.14%      23.635us        13.14%      23.635us       7.878us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                        triton_poi_fused_add_mul_tanh_0         0.00%       0.000us         0.00%       0.000us       0.000us      97.920us        13.82%      97.920us      32.640us             3  \n",
      "                                  cudaDeviceSynchronize        53.74%      96.694us        53.74%      96.694us      96.694us       0.000us         0.00%       0.000us       0.000us             1  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "Self CPU time total: 179.923us\n",
      "Self CUDA time total: 708.318us\n",
      "\n"
     ]
    }
   ],
   "source": [
    "act_fused = GeGLU(2048, 2048, impl=\"fast\").cuda()\n",
    "act_fused.load_state_dict(act.state_dict())\n",
    "\n",
    "p = profile_model(act_fused, {'input': act_input}, ProfilerConfig(amp=False))\n",
    "print(p.key_averages())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.testing.assert_close(act(act_input), act_fused(act_input))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see this method gives a gain of almost two times in speed "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Liger Kernels"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is also a great and simple example on fusing + triton from Liger Kernels repo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import triton\n",
    "import triton.language as tl\n",
    "\n",
    "from liger_kernel.ops.utils import calculate_settings\n",
    "from liger_kernel.ops.utils import ensure_contiguous\n",
    "\n",
    "\n",
    "@triton.jit\n",
    "def _silu(x):\n",
    "    return x * tl.sigmoid(x)\n",
    "\n",
    "\n",
    "@triton.jit\n",
    "def _swiglu_forward_kernel(a_ptr, b_ptr, c_ptr, stride, n_cols: tl.constexpr, BLOCK_SIZE: tl.constexpr):\n",
    "    program_id = tl.program_id(0).to(tl.int64)\n",
    "\n",
    "    # locate start index\n",
    "    a_ptr += program_id * stride\n",
    "    b_ptr += program_id * stride\n",
    "    c_ptr += program_id * stride\n",
    "\n",
    "    col_offsets = tl.arange(0, BLOCK_SIZE)\n",
    "    mask = col_offsets < n_cols\n",
    "\n",
    "    # sigmoid requires type float32\n",
    "    a_row = tl.load(a_ptr + col_offsets, mask=mask, other=0).to(tl.float32)\n",
    "    b_row = tl.load(b_ptr + col_offsets, mask=mask, other=0)\n",
    "    c_row = _silu(a_row) * b_row\n",
    "    tl.store(c_ptr + col_offsets, c_row, mask=mask)\n",
    "\n",
    "\n",
    "@triton.jit\n",
    "def _swiglu_backward_kernel(dc_ptr, a_ptr, b_ptr, stride, n_cols: tl.constexpr, BLOCK_SIZE: tl.constexpr):\n",
    "    program_id = tl.program_id(0).to(tl.int64)\n",
    "\n",
    "    # locate start index\n",
    "    dc_ptr += program_id * stride\n",
    "    a_ptr += program_id * stride\n",
    "    b_ptr += program_id * stride\n",
    "\n",
    "    col_offsets = tl.arange(0, BLOCK_SIZE)\n",
    "    mask = col_offsets < n_cols\n",
    "\n",
    "    dc_row = tl.load(dc_ptr + col_offsets, mask=mask, other=0)\n",
    "    # sigmoid requires type float32\n",
    "    a_row = tl.load(a_ptr + col_offsets, mask=mask, other=0).to(tl.float32)\n",
    "    b_row = tl.load(b_ptr + col_offsets, mask=mask, other=0)\n",
    "\n",
    "    # recomputation to save memory\n",
    "    sig_a = tl.sigmoid(a_row)\n",
    "    silu_a = a_row * sig_a\n",
    "    db_row = dc_row * silu_a\n",
    "    da_row = dc_row * (silu_a * (1 - sig_a) + sig_a) * b_row\n",
    "\n",
    "    tl.store(a_ptr + col_offsets, da_row, mask=mask)\n",
    "    tl.store(b_ptr + col_offsets, db_row, mask=mask)\n",
    "\n",
    "\n",
    "def swiglu_forward(a, b):\n",
    "    ori_shape = a.shape\n",
    "\n",
    "    n_cols = ori_shape[-1]\n",
    "    a = a.view(-1, n_cols)\n",
    "    b = b.view(-1, n_cols)\n",
    "    c = torch.empty_like(a)\n",
    "    n_rows = a.shape[0]\n",
    "\n",
    "    BLOCK_SIZE, num_warps = calculate_settings(n_cols)\n",
    "\n",
    "    _swiglu_forward_kernel[(n_rows,)](\n",
    "        a,\n",
    "        b,\n",
    "        c,\n",
    "        c.stride(-2),\n",
    "        n_cols=n_cols,\n",
    "        BLOCK_SIZE=BLOCK_SIZE,\n",
    "        num_warps=num_warps,\n",
    "    )\n",
    "    return a, b, c.view(*ori_shape)\n",
    "\n",
    "\n",
    "def swiglu_backward(a, b, dc):\n",
    "    ori_shape = dc.shape\n",
    "    n_cols = ori_shape[-1]\n",
    "    dc = dc.view(-1, n_cols)\n",
    "    n_rows = dc.shape[0]\n",
    "\n",
    "    BLOCK_SIZE, num_warps = calculate_settings(n_cols)\n",
    "\n",
    "    _swiglu_backward_kernel[(n_rows,)](\n",
    "        dc,\n",
    "        a,\n",
    "        b,\n",
    "        dc.stride(-2),\n",
    "        n_cols=n_cols,\n",
    "        BLOCK_SIZE=BLOCK_SIZE,\n",
    "        num_warps=num_warps,\n",
    "    )\n",
    "    return a.view(*ori_shape), b.view(*ori_shape)\n",
    "\n",
    "\n",
    "class LigerSiLUMulFunction(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    @ensure_contiguous\n",
    "    def forward(ctx, a, b):\n",
    "        a, b, c = swiglu_forward(a, b)\n",
    "        ctx.save_for_backward(a, b)\n",
    "        return c\n",
    "\n",
    "    @staticmethod\n",
    "    @ensure_contiguous\n",
    "    def backward(ctx, dc):\n",
    "        a, b = ctx.saved_tensors\n",
    "        a, b = swiglu_backward(a, b, dc)\n",
    "        return a, b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SwigluLiger(nn.Module):\n",
    "    def __init__(self, d_in: int, d_out: int):\n",
    "        super().__init__()\n",
    "        self.proj = nn.Linear(d_in, d_out * 2)\n",
    "\n",
    "    def forward(self, input: torch.Tensor):\n",
    "        x, gate = self.proj(input).chunk(2, dim=-1)\n",
    "        return LigerSiLUMulFunction.apply(x, gate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                                   Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                 _swiglu_forward_kernel         0.00%       0.000us         0.00%       0.000us       0.000us     135.552us        14.52%     135.552us      33.888us             4  \n",
      "                                        cudaMemsetAsync         3.79%      15.730us         3.79%      15.730us       5.243us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                  cudaFuncGetAttributes         3.02%      12.533us         3.02%      12.533us       2.089us       0.000us         0.00%       0.000us       0.000us             6  \n",
      "                                        Memset (Device)         0.00%       0.000us         0.00%       0.000us       0.000us       2.368us         0.25%       2.368us       0.789us             3  \n",
      "                                    cudaLaunchKernelExC         8.30%      34.391us         8.30%      34.391us      11.464us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "sm90_xmma_gemm_f32f32_tf32f32_f32_tn_n_tilesize128x1...         0.00%       0.000us         0.00%       0.000us       0.000us     603.613us        64.67%     603.613us     201.204us             3  \n",
      "                                       cudaLaunchKernel        10.15%      42.066us        10.15%      42.066us       7.011us       0.000us         0.00%       0.000us       0.000us             6  \n",
      "                                         cuLaunchKernel         4.51%      18.695us         4.51%      18.695us       6.232us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "void at::native::elementwise_kernel<128, 2, at::nati...         0.00%       0.000us         0.00%       0.000us       0.000us     191.872us        20.56%     191.872us      31.979us             6  \n",
      "                                  cudaDeviceSynchronize        70.23%     291.173us        70.23%     291.173us     291.173us       0.000us         0.00%       0.000us       0.000us             1  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "Self CPU time total: 414.588us\n",
      "Self CUDA time total: 933.405us\n",
      "\n"
     ]
    }
   ],
   "source": [
    "act_fused = SwigluLiger(2048, 2048).cuda()\n",
    "act_fused.load_state_dict(act.state_dict())\n",
    "\n",
    "p = profile_model(act_fused, {'input': act_input}, ProfilerConfig(amp=False))\n",
    "print(p.key_averages())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here you can check a lot of great examples of how to fuse operations and write triton kernels. [Liger Kernels repo](https://github.com/linkedin/Liger-Kernel/tree/main/src/liger_kernel/ops), [Liger Kernels paper](https://arxiv.org/pdf/2410.10989)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Flash Attention\n",
    "\n",
    "Flash Attention is a highly efficient attention mechanism designed to significantly reduce the computational cost and memory usage associated with processing large-scale inputs in Transformer models. It leverages a novel algorithm that allows for the computation of attention weights and the aggregation of context vectors in a single pass, improving both speed and efficiency. This innovation enables the handling of longer sequences in natural language processing tasks and other applications, making Transformers more scalable and practical for a broader range of datasets and computational constraints.\n",
    "\n",
    "[FlashAttention](https://arxiv.org/pdf/2205.14135)\n",
    "\n",
    "[FlassAttention2](https://arxiv.org/pdf/2307.08691)\n",
    "\n",
    "[FlassAttention3](https://arxiv.org/pdf/2407.08608)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tokens = 1024\n",
    "seqlens_q = [400, 500, 124]\n",
    "seqlens_q = [0, 400, 900, 1024]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def flash_attn_varlen_func(\n",
    "    q,\n",
    "    k,\n",
    "    v,\n",
    "    cu_seqlens_q,\n",
    "    cu_seqlens_k,\n",
    "    max_seqlen_q,\n",
    "    max_seqlen_k,\n",
    "    dropout_p=0.0,\n",
    "    softmax_scale=None,\n",
    "    causal=False,\n",
    "    window_size=(-1, -1),  # -1 means infinite context window\n",
    "    softcap=0.0, # 0.0 means deactivated\n",
    "    alibi_slopes=None,\n",
    "    deterministic=False,\n",
    "    return_attn_probs=False,\n",
    "    block_table=None,\n",
    "):\n",
    "    \"\"\"dropout_p should be set to 0.0 during evaluation\n",
    "    Supports multi-query and grouped-query attention (MQA/GQA) by passing in K, V with fewer heads\n",
    "    than Q. Note that the number of heads in Q must be divisible by the number of heads in KV.\n",
    "    For example, if Q has 6 heads and K, V have 2 heads, head 0, 1, 2 of Q will attention to head\n",
    "    0 of K, V, and head 3, 4, 5 of Q will attention to head 1 of K, V.\n",
    "\n",
    "    If causal=True, the causal mask is aligned to the bottom right corner of the attention matrix.\n",
    "    For example, if seqlen_q = 2 and seqlen_k = 5, the causal mask (1 = keep, 0 = masked out) is:\n",
    "        1 1 1 1 0\n",
    "        1 1 1 1 1\n",
    "    If seqlen_q = 5 and seqlen_k = 2, the causal mask is:\n",
    "        0 0\n",
    "        0 0\n",
    "        0 0\n",
    "        1 0\n",
    "        1 1\n",
    "    If the row of the mask is all zero, the output will be zero.\n",
    "    \"\"\"\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DummyAttention(nn.Module):\n",
    "    def forward(self, q, k, v):\n",
    "        return F.scaled_dot_product_attention(q, k, v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "dummy_attention = DummyAttention()\n",
    "dtype = torch.bfloat16"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch.nn.attention import SDPBackend, sdpa_kernel"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                                   Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "void pytorch_flash::flash_fwd_kernel<pytorch_flash::...         0.00%       0.000us         0.00%       0.000us       0.000us      58.893ms       100.00%      58.893ms      11.779ms             5  \n",
      "                                  cudaStreamIsCapturing         0.04%      15.744us         0.04%      15.744us       5.248us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                   cudaFuncSetAttribute         0.03%      13.393us         0.03%      13.393us       4.464us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                       cudaLaunchKernel         0.32%     142.219us         0.32%     142.219us      47.406us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                  cudaDeviceSynchronize        99.61%      43.798ms        99.61%      43.798ms      43.798ms       0.000us         0.00%       0.000us       0.000us             1  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "Self CPU time total: 43.969ms\n",
      "Self CUDA time total: 58.893ms\n",
      "\n"
     ]
    }
   ],
   "source": [
    "q = torch.randn(64, 8192, 8, 128, dtype=dtype, device=\"cuda\")\n",
    "k = torch.randn(64, 8192, 8, 128, dtype=dtype, device=\"cuda\")\n",
    "v = torch.randn(64, 8192, 8, 128, dtype=dtype, device=\"cuda\")\n",
    "\n",
    "with sdpa_kernel(SDPBackend.FLASH_ATTENTION):\n",
    "    p = profile_model(dummy_attention, {'q': q, 'k': k, 'v': v}, ProfilerConfig(amp=False))\n",
    "    print(p.key_averages())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                                   Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "                                       cudaLaunchKernel         0.22%     358.657us         0.22%     358.657us       5.978us       0.000us         0.00%       0.000us       0.000us            60  \n",
      "                                 cudaDeviceGetAttribute         0.00%       1.502us         0.00%       1.502us       0.501us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                         cuLaunchKernel         0.01%      13.408us         0.01%      13.408us       4.469us       0.000us         0.00%       0.000us       0.000us             3  \n",
      "                                    cudaLaunchKernelExC         0.08%     129.140us         0.08%     129.140us       4.783us       0.000us         0.00%       0.000us       0.000us            27  \n",
      "                                  cudaDeviceSynchronize        99.68%     158.913ms        99.68%     158.913ms     158.913ms       0.000us         0.00%       0.000us       0.000us             1  \n",
      "void at::native::unrolled_elementwise_kernel<at::nat...         0.00%       0.000us         0.00%       0.000us       0.000us      39.568ms        34.62%      39.568ms       2.198ms            18  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us      16.930ms        14.81%      16.930ms       1.411ms            12  \n",
      "void gemmSN_TN_kernel<float, 128, 16, 2, 4, 8, 9, fa...         0.00%       0.000us         0.00%       0.000us       0.000us      16.983ms        14.86%      16.983ms     353.814us            48  \n",
      "void cutlass::Kernel2<cutlass_80_tensorop_s1688gemm_...         0.00%       0.000us         0.00%       0.000us       0.000us      32.895us         0.03%      32.895us       5.482us             6  \n",
      "void (anonymous namespace)::softmax_warp_forward<flo...         0.00%       0.000us         0.00%       0.000us       0.000us     679.738us         0.59%     679.738us     113.290us             6  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us     348.959us         0.31%     348.959us      58.160us             6  \n",
      "void at::native::reduce_kernel<512, 1, at::native::R...         0.00%       0.000us         0.00%       0.000us       0.000us       1.886ms         1.65%       1.886ms     314.307us             6  \n",
      "void at::native::vectorized_elementwise_kernel<4, at...         0.00%       0.000us         0.00%       0.000us       0.000us       9.856us         0.01%       9.856us       1.643us             6  \n",
      "void at::native::elementwise_kernel<128, 2, at::nati...         0.00%       0.000us         0.00%       0.000us       0.000us     741.658us         0.65%     741.658us     123.610us             6  \n",
      "void at::native::vectorized_elementwise_kernel<8, at...         0.00%       0.000us         0.00%       0.000us       0.000us       6.623ms         5.80%       6.623ms     551.912us            12  \n",
      "sm80_xmma_gemm_f32f32_f32f32_f32_nn_n_tilesize32x32x...         0.00%       0.000us         0.00%       0.000us       0.000us      30.482ms        26.67%      30.482ms     564.489us            54  \n",
      "-------------------------------------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  \n",
      "Self CPU time total: 159.416ms\n",
      "Self CUDA time total: 114.285ms\n",
      "\n"
     ]
    }
   ],
   "source": [
    "with sdpa_kernel(SDPBackend.MATH):\n",
    "    p = profile_model(dummy_attention, {'q': q, 'k': k, 'v': v}, ProfilerConfig(amp=False))\n",
    "    print(p.key_averages())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Flex Attention\n",
    "\n",
    "More flexible approach to efficient attention implementation. [Flex Attention Tutorial](https://pytorch.org/blog/flexattention/).\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Score Mod"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def noop(score, b, h, q_idx, kv_idx):\n",
    "    return score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def relative_positional(score, b, h, q_idx, kv_idx):\n",
    "    return score + (q_idx - kv_idx)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "def causal_mask(score, b, h, q_idx, kv_idx):\n",
    "    return torch.where(q_idx >= kv_idx, score, -float(\"inf\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "dtype = torch.float16\n",
    "q = torch.randn(64, 1024, 8, 128, dtype=dtype, device=\"cuda\")\n",
    "k = torch.randn(64, 1024, 8, 128, dtype=dtype, device=\"cuda\")\n",
    "v = torch.randn(64, 1024, 8, 128, dtype=dtype, device=\"cuda\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(0.0039, device='cuda:0', dtype=torch.float16)"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from torch.nn.attention.flex_attention import flex_attention\n",
    "from torch.nn.attention import SDPBackend, sdpa_kernel\n",
    "\n",
    "with sdpa_kernel(SDPBackend.FLASH_ATTENTION):\n",
    "    flash = F.scaled_dot_product_attention(q, k, v)\n",
    "\n",
    "flex = flex_attention(q, k, v, score_mod=noop)\n",
    "\n",
    "torch.max(torch.abs(flex - flash))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Mask Mods"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# returns True if this position should participate in the computation\n",
    "# mask_mod(b, h, q_idx, kv_idx) => bool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch.nn.attention.flex_attention import create_block_mask\n",
    "\n",
    "def causal(b, h, q_idx, kv_idx):\n",
    "    return q_idx >= kv_idx\n",
    "\n",
    "# Because the sparsity pattern is independent of batch and heads, we'll set them to None (which broadcasts them) \n",
    "block_mask = create_block_mask(causal, B=None, H=None, Q_LEN=1024, KV_LEN=1024)\n",
    "flex_attention(q, k, v, block_mask=block_mask)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "SLIDING_WINDOW = 1024\n",
    "\n",
    "def sliding_window_causal(b, h, q_idx, kv_idx):\n",
    "    causal_mask = q_idx >= kv_idx\n",
    "    window_mask = q_idx - kv_idx <= SLIDING_WINDOW \n",
    "    return causal_mask & window_mask"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![prefixLM](./images/prefixLM.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "prefix_length = torch.tensor([20, 10]).cuda()\n",
    "def prefix_lm_causal(b, h, q_idx, kv_idx):\n",
    "    causal_mask = q_idx >= kv_idx\n",
    "    return causal_mask | (kv_idx <= prefix_length[b])\n",
    "\n",
    "# In this case, our mask is different per sequence so we set B equal to our batch size\n",
    "block_mask = create_block_mask(prefix_lm_causal, B=2, H=None, Q_LEN=64, KV_LEN=64)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
